﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace MashGen.Engine.Expressions.Formatter
{
    public abstract class AbstractFormatter<P> : IExpressionVisitor<object, P>
    {
        private const string UNSUPPORTED_TYPE_MESSAGE = "Unsupported type.";

        private const int NUMBER_PRECEDENCE   =  0;
        private const int VARIABLE_PRECEDENCE =  0;
        private const int NEGATIVE_PRECEDENCE = 10;
        private const int SUM_PRECEDENCE      = 20;
        private const int PRODUCT_PRECEDENCE  = 30;
        private const int QUOTIENT_PRECEDENCE = 30;
        private const int POWER_PRECEDENCE    = 40;

        private Dictionary<Type, int> precedences;

        public AbstractFormatter()
        {
            precedences = new Dictionary<Type, int> {
                { typeof(Number),   NUMBER_PRECEDENCE },
                { typeof(Variable), VARIABLE_PRECEDENCE },
                { typeof(Negative), NEGATIVE_PRECEDENCE },
                { typeof(Sum),      SUM_PRECEDENCE },
                { typeof(Product),  PRODUCT_PRECEDENCE },
                { typeof(Quotient), QUOTIENT_PRECEDENCE },
                { typeof(Power),    POWER_PRECEDENCE }
            };
        }

        object IExpressionVisitor<object, P>.VisitNumber(Number number, P parameter)
        {
            PrintNumber(number.Value, parameter);
            return null;
        }

        object IExpressionVisitor<object, P>.VisitVariable(Variable variable, P parameter)
        {
            PrintVariable(variable.Name, parameter);
            return null;
        }

        object IExpressionVisitor<object, P>.VisitNegative(Negative negative, P parameter)
        {
            int expressionPrecedence;
            if (!precedences.TryGetValue(negative.Expression.GetType(), out expressionPrecedence))
            {
                throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
            }
            PrintOperator(Operator.UnaryMinus, parameter);
            if (expressionPrecedence > NEGATIVE_PRECEDENCE)
            {
                OpenParenthesis(parameter);
                negative.Expression.Vist(this, parameter);
                CloseParenthesis(parameter);
            }
            else
            {
                negative.Expression.Vist(this, parameter);
            }
            return null;
        }

        object IExpressionVisitor<object, P>.VisitSum(Sum sum, P parameter)
        {
            bool first = true;
            foreach (var summand in sum.Summands)
            {
                AbstractExpression expression; 
                if (first)
                {
                    expression = summand;
                    first = false;
                }
                else
                {
                    var negative = summand as Negative;
                    if (negative != null)
                    {
                        PrintOperator(Operator.BinaryMinus, parameter);
                        expression = negative.Expression;
                    }
                    else
                    {
                        PrintOperator(Operator.Plus, parameter);
                        expression = summand;
                    }
                }

                int expressionPrecedence;
                if (!precedences.TryGetValue(expression.GetType(), out expressionPrecedence))
                {
                    throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
                }
                if (expressionPrecedence > 0 && expressionPrecedence < SUM_PRECEDENCE)
                {
                    OpenParenthesis(parameter);
                    expression.Vist(this, parameter);
                    CloseParenthesis(parameter);
                }
                else
                {
                    expression.Vist(this, parameter);
                }
            }
            return null;
        }

        object IExpressionVisitor<object, P>.VisitProduct(Product product, P parameter)
        {
            bool first = true;
            Type leftType = null;
            Type rightType = null;
            foreach (var factor in product.Factors)
            {
                rightType = factor.GetType();
                if (first)
                {
                    first = false;
                }
                else
                {
                    if (leftType == typeof(Number) && rightType == typeof(Number))
                    {
                        PrintOperator(Operator.StrongDot, parameter);
                    }
                    else
                    {
                        PrintOperator(Operator.WeakDot, parameter);
                    }
                }

                int factorPrecedence;
                if (!precedences.TryGetValue(factor.GetType(), out factorPrecedence))
                {
                    throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
                }
                if (factorPrecedence > 0 && factorPrecedence < PRODUCT_PRECEDENCE)
                {
                    OpenParenthesis(parameter);
                    factor.Vist(this, parameter);
                    CloseParenthesis(parameter);
                }
                else
                {
                    factor.Vist(this, parameter);
                }

                leftType = rightType;
            }
            return null;
        }

        object IExpressionVisitor<object, P>.VisitQuotient(Quotient quotient, P parameter)
        {
            OpenQuotient(parameter);

            int precedence;
            bool needsParenthesis;

            if (!precedences.TryGetValue(quotient.Numerator.GetType(), out precedence))
            {
                throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
            }
            needsParenthesis = precedence > 0 && precedence <= QUOTIENT_PRECEDENCE;
            OpenNumerator(parameter, needsParenthesis);
            quotient.Numerator.Vist(this, parameter);
            CloseNumerator(parameter, needsParenthesis);

            if (!precedences.TryGetValue(quotient.Denominator.GetType(), out precedence))
            {
                throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
            }
            needsParenthesis = precedence > 0 && precedence <= QUOTIENT_PRECEDENCE;
            OpenDenominator(parameter, needsParenthesis);
            quotient.Denominator.Vist(this, parameter);
            CloseDenominator(parameter, needsParenthesis);

            CloseQuotient(parameter);
            return null;
        }

        object IExpressionVisitor<object, P>.VistPower(Power power, P parameter)
        {
            OpenPower(parameter);

            int precedence;
            bool needsParenthesis;

            if (!precedences.TryGetValue(power.Radix.GetType(), out precedence))
            {
                throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
            }
            needsParenthesis = precedence > 0 && precedence <= POWER_PRECEDENCE;
            OpenRadix(parameter, needsParenthesis);
            power.Radix.Vist(this, parameter);
            CloseRadix(parameter, needsParenthesis);

            if (!precedences.TryGetValue(power.Exponent.GetType(), out precedence))
            {
                throw new ArgumentException(UNSUPPORTED_TYPE_MESSAGE);
            }
            needsParenthesis = precedence > 0 && precedence <= POWER_PRECEDENCE;
            OpenExponent(parameter, needsParenthesis);
            power.Exponent.Vist(this, parameter);
            CloseExponent(parameter, needsParenthesis);

            CloseQuotient(parameter);
            return null;
        }

        protected abstract void PrintNumber(uint value, P parameter);
        protected abstract void PrintVariable(string name, P parameter);
        protected abstract void PrintOperator(Operator op, P parameter);

        protected abstract void OpenParenthesis(P parameter);
        protected abstract void CloseParenthesis(P parameter);

        protected abstract void OpenQuotient(P parameter);
        protected abstract void OpenNumerator(P parameter, bool needsParenthesis);
        protected abstract void CloseNumerator(P parameter, bool needsParenthesis);
        protected abstract void OpenDenominator(P parameter, bool needsParenthesis);
        protected abstract void CloseDenominator(P parameter, bool needsParenthesis);
        protected abstract void CloseQuotient(P parameter);

        protected abstract void OpenPower(P parameter);
        protected abstract void OpenRadix(P parameter, bool needsParenthesis);
        protected abstract void CloseRadix(P parameter, bool needsParenthesis);
        protected abstract void OpenExponent(P parameter, bool needsParenthesis);
        protected abstract void CloseExponent(P parameter, bool needsParenthesis);
        protected abstract void ClosePower(P parameter);
    }
}
